import threading
import msvcrt
import serial
import struct
import time
import sys
import os
import subprocess
from serial.tools.list_ports import comports  # 用于获取串口列表
from tqdm import tqdm

# 串口参数定义
PORT = "COM13"
BUADRATE = 2000000
TIMEOUT = 2
# 文件定义
origin_bin = "GB2312_80_new.bin"
check_bin = "test.bin"
# 命令字节定义
ERASE_CHIP = 0xABCDEF01
PROGRAM = 0xABCDEF02
READ_CHIP = 0xABCDEF03
PROGRAM_EXIT = 0xABCDEF04
WRITE_ZK_OK_FLAG = 0xABCDEF05
GET_FLASH_ID = 0xABCDEF06
YES_RE_FLASH = 0xABCDEF07

# 握手命令
START_RECEIVE_TASK = 0x00
TASK_FINISH = 0x01
READ_PRAMA = 0x02
ERROR = 0x03
START_SEND_DATA = 0x04
TASK_BUSY = 0x05
RE_PROGRAM_FLASH = 0x06

def select_and_open_serial():
    # 1. 获取所有可用串口
    ports = comports()  # 返回ListPortInfo对象列表

    # 2. 检查是否有可用串口
    if not ports:
        print("未检测到任何可用串口，请检查设备连接后重试。")
        return None

    # 3. 打印串口信息（供用户选择）
    print("=" * 60)
    print("可用串口列表：")
    for i, port in enumerate(ports, 1):  # 序号从1开始
        print(f"\n序号 {i}:")
        print(f"  端口路径: {port.device}")  # 如Windows的"COM3"、Linux的"/dev/ttyUSB0"
        print(f"  设备描述: {port.description or '未知'}")  # 设备名称（如"USB Serial Port"）
        print(f"  制造商: {port.manufacturer or '未知'}")  # 制造商信息
        print(f"  产品ID: {port.product or '未知'}")  # 产品型号信息
    print("\n" + "=" * 60)

    # 4. 让用户选择串口（处理输入合法性）
    while True:
        try:
            choice = int(input(f"请输入要打开的串口序号（1-{len(ports)}）："))
            if 1 <= choice <= len(ports):
                selected_port = ports[choice - 1]  # 转换为列表索引
                break
            else:
                print(f"输入无效！请输入1到{len(ports)}之间的数字。")
        except ValueError:
            print("输入无效！请输入数字。")

    # 5. 打开选择的串口（可自定义波特率等参数）
    try:
        # 常用参数：端口路径、波特率、超时时间（单位秒）
        ser = serial.Serial(
            port=selected_port.device,
            baudrate=BUADRATE,   # 可根据设备需求修改（如115200）
            timeout=TIMEOUT      # 读取超时时间
        )
        if ser.is_open:
            print(f"\n成功打开串口：{selected_port.device}（波特率：9600）")
            return ser
        else:
            print("串口打开失败（未知错误）。")
            return None
    except serial.SerialException as e:
        print(f"打开串口失败：{str(e)}")
        print("可能原因：串口被占用、权限不足（Linux/macOS需root或dialout组权限）。")
        return None

def count_two_file_differences(src:str, dest:str):
    # 打开两个文件以进行二进制读取
    with open(src, "rb") as file1, open(dest, "rb") as file2:
        content1 = file1.read()
        content2 = file2.read()
        # 确保两个文件具有相同的长度
        if len(content1) != len(content2):
            print("文件长度不同，无法比较字节差异, 写入不成功>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
            return
        # 逐个字节比较文件内容，并计算差异的字节数
        byte_diff_count = sum(b1 != b2 for b1, b2 in zip(content1, content2))
    if byte_diff_count == 0:
        print(f"比较了读取的数据,数据完全相同, 校验成功!!!!")
    else:
        print(f"Flash中数据与原文件有 {byte_diff_count} 字节的差异, 校验不成功!!!!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")

# STM32会发送START_RECEIVE_TASK后,等待100ms接收命令
def wait_start_sign():
    ser.timeout = 0.1
    # while ser.read(10):
    #     pass
    while True:
        data = ser.read(1)
        if len(data) == 1 and data[0] == START_RECEIVE_TASK:
            return

# STM32需要数据时会发出相应的信号,根据信号来传输数据
def wait_sign(sign: int, timeout: float = 0.1, msg: str = "Timeout Error"):
    ser.timeout = timeout
    data = ser.read(1)
    if len(data) == 1 and data[0] == sign:
        return
    else:
        tqdm.write(msg + f" wait sign = {sign} timeout = {timeout}")
        sys.exit()

def wait_sign_not_exit(sign: int, timeout: float = 0.1, msg: str = "Timeout Error"):
    ser.timeout = timeout
    data = ser.read(1)
    if len(data) == 1 and data[0] == sign:
        return 1
    else:
        return 0

# 长时间等待信号,用于清空芯片时提示
def wait_sign_with_busy_ack(sign: int, timeout: float = 0.2, msg: str = "等待忙信号标志时发生错误"):
    ser.timeout = timeout
    while True:
        data = ser.read(1)
        if len(data) == 1 and data[0] == sign:
            return
        elif len(data) == 1 and data[0] == TASK_BUSY:
            print(".", end="", flush=True)
        else:
            print("\n" + msg + f" wait sign = {sign} timeout = {timeout}")
            sys.exit()

def write_zk_OK_flag_to_flash():
    print("准备向字库写入字库已写好标志")
    wait_start_sign()
    ser.write(struct.pack("<I", WRITE_ZK_OK_FLAG))
    wait_sign(TASK_FINISH, 0.5, "等待STM32返回写好标志信号时超时了,标志好像没写好!")

def read_size_of_data(addr, size):
    # 同步主机发出信号
    wait_start_sign()
    ser.write(struct.pack("<I", READ_CHIP))
    # 发送地址
    wait_sign(READ_PRAMA, timeout=0.2, msg="等待传送读取数量时超时")
    ser.write(struct.pack("<I", addr))
    # 发送数量
    wait_sign(READ_PRAMA, timeout=0.2, msg="等待传送读取地址时超时")
    ser.write(struct.pack("<I", size))
    # 开始接收
    wait_sign(START_SEND_DATA, timeout=0.2, msg="等待开始发送数据时超时")
    ser.timeout = 1
    data = ser.read(size)
    # 未收到,打印超时
    if(len(data) < size):
        tqdm.write(f"从 {addr} 读取 {size} 个字节时时出现了超时, 只读到了 {len(data)} 个字节数据.")
    return data
    
def read_data_from_flash(size:int, addr:int, dest_filename:str):
    t_start = time.time()
    bytes_read = 0
    n = 0
    pbar = tqdm(total=size, ncols=100, unit="B", unit_scale=True)
    tqdm.write(f"正在从 {addr} 读取 {size} 字节的芯片内容,保存到 {check_bin} 中")
    with open(dest_filename, "wb") as f:
        while True:
            # 是否读取完成
            if size - bytes_read == 0:
                # 打印读取完成
                tqdm.write(f"读取用时: {time.time()-t_start:0.3f},一共读取了 {bytes_read} 个字节")
                return
            # 读4096字节,或者更少
            if size - bytes_read >= 4096:
                n = 4096
            else:
                n = size - bytes_read;
            data = read_size_of_data(addr, n)
            # 自增地址
            addr += n
            bytes_read += n
            # 写入文件
            f.write(data)
            # 更新进度条
            pbar.update(n)

def erase_chip():
    t_start = time.time()
    print(f"正在擦除芯片内容,等待开始")
    wait_start_sign()
    print(f"擦除开始", end="")
    ser.write(struct.pack("<I", ERASE_CHIP))
    wait_sign_with_busy_ack(TASK_FINISH)
    print(f"\n整片擦除用时: {time.time()-t_start:0.3f}")
    time.sleep(0.2)

def write_file_to_flash(filename:str, addr:int):
    t_start = time.time()
    pbar = tqdm(total=os.path.getsize(filename), ncols=100, unit="B", unit_scale=True)
    tqdm.write(f"正在将 {filename} 写入Flash的 {addr} 处, 一共 {os.path.getsize(filename)} 字节。")
    with open(filename, "rb") as file:
        while True:
            # 发送下载命令
            wait_start_sign()
            ser.write(struct.pack("<I", PROGRAM))
            # 发送地址
            wait_sign(READ_PRAMA)
            ser.write(struct.pack("<I", addr))
            # 读取文件
            data = file.read(4096)
            # 地址递增,并检查是否需要继续写入
            if len(data) != 0:
                addr += len(data)
                pbar.update(len(data))
            else:
                break
            # 发送size
            wait_sign(READ_PRAMA)
            ser.write(struct.pack("<I", len(data)))
            # 发送数据
            wait_sign(READ_PRAMA)
            ser.write(data)
            # 等待完成
            wait_sign(TASK_FINISH, 1.0)

def program_exit():
    # 发送命令
    wait_start_sign()
    ser.write(struct.pack("<I", PROGRAM_EXIT))
    wait_sign(TASK_FINISH, 0.5, "等待结束编程任务时超时")
    print("编程结束，LED进入显示程序")

def read_flash_id():
    print("准备读取FLASH ID,等待主机发出开始传输消息信号")
    wait_start_sign()
    ser.write(struct.pack("<I", GET_FLASH_ID))
    wait_sign(START_SEND_DATA, timeout=1.0, msg="等待发送FLASH ID时超时了")
    ret = ser.read(2)
    if len(ret) == 2:
        print(f"你所使用的FLASH型号为 {ret} ")

def count_sequence(data: bytes, sequence: bytes) -> int:  
    count = 0  
    length = len(sequence)  
    for i in range(len(data) - length + 1):  
        if data[i:i+length] == sequence:  
            count += 1  
    return count 

def reboot_target_and_re_flash():
    print("正在重启目标系统", flush=True)
    command = f"make flash"
    subprocess.run(command, shell=True, cwd="../")
    command = f"pyocd reset -t AIR32F103CC --pack ../Libs/Keil.AIR32F103_DFP.1.1.9.pack"
    subprocess.run(command, shell=True)
    print("重启完成, 发送信号给AIR32, 开始重新烧写", flush=True)
    print("发送重新FLASH命令", flush=True)
    ser.write(struct.pack("<I", YES_RE_FLASH))
    ser.timeout = 0.1
    print("清除串口缓冲区", flush=True)
    while True:
        data = ser.read(1000)
        if(len(data) < 1000):
            break
    while True:
        data = ser.read(100)
        if(len(data) == 0):
            break

def write_and_check(filename:str, addr:int):
    file_size = os.path.getsize(filename)
    last_addr = addr + file_size
    write_file_to_flash(filename, addr)
    read_data_from_flash(file_size, addr, "temp.bin")
    count_two_file_differences(filename, "temp.bin")    
    return (addr, file_size, last_addr)

def write_with_no_check(filename:str, addr:int):
    file_size = os.path.getsize(filename)
    last_addr = addr + file_size
    write_file_to_flash(filename, addr)
    return (addr, file_size, last_addr)

if __name__ == "__main__":
    os.system("cls")
    ser = select_and_open_serial()
    # 读取FLASH ID
    reboot_target_and_re_flash()
    read_flash_id()
    # erase_chip()
    list = []
    last = write_and_check("GB2312_80_new.bin", 0x1000)
    list.append(last)
    last = write_and_check("ac3/22050_0.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_1.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_2.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_3.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_4.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_5.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_6.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_7.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_8.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_9.ac3", last[2] + 1)
    list.append(last)                        
    last = write_and_check("ac3/22050_10.ac3", last[2] + 1)
    list.append(last)                        
    last = write_and_check("ac3/22050_beep1.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_beep2.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_min.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_point.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_ready.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sec.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_tec.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_261hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_293hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_329hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_349hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_392hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_440hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_493hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_523hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_587hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_659hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_698hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_783hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_880hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_987hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_1046hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_1174hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_1318hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_1396hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_1567hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_1760hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_1975hz.ac3", last[2] + 1)
    list.append(last)
    last = write_and_check("ac3/22050_sin_200ms_2093hz.ac3", last[2] + 1)
    list.append(last)
    
    with open(f"list.bin", "wb") as f:
        # 第一个8字节，前4字节：文件个数，后4字节：最后地址
        f.write(struct.pack("<I", len(list)))      # 存储有多少个文件在flash中
        f.write(struct.pack("<I", list[-1:][0][2]))    # 最后使用到的地址
        # 每个文件占8字节 前4字节：开始地址，后4字节：长度
        for ele in list:
            f.write(struct.pack("<I", ele[0]))
            f.write(struct.pack("<I", ele[1]))
            f.flush

    last = write_and_check("list.bin", 0)
    list.append(last)
    
    print(list)
    print(f"一共写入了{len(list) - 1}个文件。共占用了{list[-2][-1]/1024:.2}kb字节")
    program_exit()


